/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.copier;

import java.lang.ref.*;
import java.util.*;
import java.util.concurrent.*;
import ch.trackedbean.common.*;
import ch.trackedbean.copier.annotations.*;
import ch.trackedbean.copier.engine.*;
import ch.trackedbean.loaders.lazy.*;
import ch.trackedbean.util.*;

/**
 * Manager holding {@link IBeanMapper}s.<br>
 * Create <code>META-INF/services/ch.trackedbean.Mapper.properties</code> holding the class name of the factory as value of the <code>factor</code> property, if
 * you want to set an other {@link IBeanMapperFactory}.
 * 
 * @author M. Hautle
 */
public final class BeanMapperManager {
	/** Empty mapper. */
	private static IBeanMapper NOP_MAPPER = new BeanMapper(Object.class, Object.class);

	/** The factory to use. */
	private static IBeanMapperFactory factory = new DefaultMapperFactory();

	/** Mapper cache. */
	private static final Map<Class<?>, Reference<IBeanMapper>> MAPPER_CACHE = new ConcurrentHashMap<Class<?>, Reference<IBeanMapper>>();

	static {
		final Properties p = ConfigurationHelper.loadSettings("ch.trackedbean.Mapper");
		if (p != null) {
			final Object f = ConfigurationHelper.create(p, "factory");
			if (f instanceof IBeanMapperFactory)
				setFactory((IBeanMapperFactory) f);
		}
		MAPPER_CACHE.put(Object.class, new SoftReference<IBeanMapper>(NOP_MAPPER));
	}

	/**
	 * Returns the mapper for the specified class
	 * 
	 * @param c The mapper target class
	 * @return The corresponding mapper
	 * @throws MapperInitialisationException If something went wrong
	 */
	public static IBeanMapper getMapper(Class<?> c) throws MapperInitialisationException {
		final Reference<IBeanMapper> ref = MAPPER_CACHE.get(c);
		IBeanMapper val = ref != null ? ref.get() : null;
		if (val == null) {
			val = factory.createMapper(c);
			if (!val.doesSomething())
				val = NOP_MAPPER;
			MAPPER_CACHE.put(c, new SoftReference<IBeanMapper>(val));
		}
		return val;
	}

	/**
	 * Copies the value from the src object into the dst object.<br>
	 * All properties except those annotated with {@link LoadLazy} were copied.
	 * 
	 * @param <T> The destination bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @return The filled destination (dst) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copySrc2Dst(Object src, T dst) {
		return copySrc2Dst(src, dst, DefaultMappingTypes.NO_LAZY);
	}

	/**
	 * Copies the value from the dst object into the src object.<br>
	 * All properties except those annotated with {@link LoadLazy} were copied.
	 * 
	 * @param <T> The source bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @return The filled source (src) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copyDst2Src(Object dst, T src) {
		return copyDst2Src(dst, src, DefaultMappingTypes.NO_LAZY);
	}

	/**
	 * Copies all values from the src object into the dst object.<br>
	 * 
	 * @param <T> The destination bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @return The filled destination (dst) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copyCompleteSrc2Dst(Object src, T dst) {
		return copySrc2Dst(src, dst, DefaultMappingTypes.ALL);
	}

	/**
	 * Copies all values from the dst object into the src object.<br>
	 * 
	 * @param <T> The source bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @return The filled source (src) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copyCompleteDst2Src(Object dst, T src) {
		return copyDst2Src(dst, src, DefaultMappingTypes.ALL);
	}

	/**
	 * Copies the value from the src object into the dst object.<br>
	 * 
	 * @param <T> The destination bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @param filter The filter to use
	 * @return The filled destination (dst) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copySrc2Dst(Object src, T dst, IMappingFilter filter) {
		return getMapper(dst.getClass()).copySrc2Dst(src, dst, new DefaultMappingContext(filter));
	}

	/**
	 * Copies the value from the dst object into the src object.<br>
	 * 
	 * @param <T> The source bean type
	 * 
	 * @param dst The object holding the mapping
	 * @param src The other object
	 * @param filter The filter to use
	 * @return The filled source (src) object
	 * @throws TechnicalException If something goes wrong
	 */
	public static <T> T copyDst2Src(Object dst, T src, IMappingFilter filter) {
		return getMapper(dst.getClass()).copyDst2Src(dst, src, new DefaultMappingContext(filter));
	}

	/**
	 * Sets the factory used to create the {@link IBeanMapper}s.
	 * 
	 * @param factory The factory to use
	 */
	public static void setFactory(IBeanMapperFactory factory) {
		if (factory == null)
			throw new IllegalArgumentException("You must set a non null value as factory!");
		BeanMapperManager.factory = factory;
	}

	/**
	 * Default implementation of {@link IBeanMapperFactory}.
	 * 
	 * @author M. Hautle
	 */
	public static class DefaultMapperFactory implements IBeanMapperFactory {
		/**
		 * {@inheritDoc}
		 */
		@Override
		public IBeanMapper createMapper(Class<?> type) throws MapperInitialisationException {
			return new DefaultBeanMapperConfigurator(type).create();
		}
	}
}
